/**
 * 
 */
package algo;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;

import serveur_reso.Reso;

/**
 * @author BAYRAMOV Matin
 *
 */
public class Abcast implements Algorithm {
	
	private int H = 0;
	private long est;
	private int nbPorte;

	private int estm;

	private int nbRcv;

	private int nbRcvEst;
	private int numPorte;
	private int nbPlaces;

	private int numMsg;

	private ListStockage recu;
	private Message msg;
	private PrintWriter trace;

	private Reso reso;
	

	public Abcast(int numPorte, int nbPorte, int nbPlaces, Reso reso) {
		this.numPorte = numPorte;
		this.nbPorte = nbPorte;
		this.nbPlaces = nbPlaces;
		this.reso = reso;

		nbRcv = 0;
		nbRcvEst = 0;
		numMsg = 0;
		recu = new ListStockage();	

		try {
			trace  = new PrintWriter(new FileWriter("trace_" + numPorte));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/*
	 * Méthode permettant de réaliser une diffusion, c'est-à-dire une demande d'initialisation
	 * pour chacune des portes (elle-même comprise)
	 */
	void diffusion(TypeDemandeEvenement typeEv) {

		synchronized(this) {

			numMsg++;
			H++;
			est = System.currentTimeMillis();
			Message msg = new Message();
			msg.setTypeDemande(TypeDemandeMessage.INITIALISATION);
			msg.setTypeEvenement(typeEv);
			msg.setEst(est);
			msg.setNumMsg(numMsg);
			for(int i = 0; i < nbPorte; i++) {
				try {
					trace.print("ENVOI: " + msg + " - de " + numPorte + " a " + i + "|");
					trace.flush();
					reso.sendMessage(numPorte, i, msg);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	/*
	 * Méthode appelée lors de la réception d'une demande d'initialisation.
	 * On envoi donc à la porte émettrice une demande de calcul d'estampille
	 */
	void receptionDiffusion(Message msg, int from, int to) {

		synchronized(this) {
			est = System.currentTimeMillis();
			if(est < msg.getEst()) est = msg.getEst();
			est += 1;

			//On ajoute dans notre liste locale notre message reçu en tant que statut "pending" (en attente)
			StockageMessage sm = new StockageMessage();
			sm.setNumMsg(msg.getNumMsg());
			sm.setTypeMessage(TypeStockageMessage.pending);
			sm.setEst(est);
			sm.setTypeEvenement(msg.getTypeEvenement());
			
			recu.addStockageMessage(sm);

			Message msgEst = new Message();
			msgEst.setTypeDemande(TypeDemandeMessage.CALCUL_EST);
			msgEst.setEst(est);
			msgEst.setTypeEvenement(msg.getTypeEvenement());
			msgEst.setNumMsg(msg.getNumMsg());

			try {
				trace.print("ENVOI: " + msgEst + " - de " + to + " a " + from + "|");
				trace.flush();
				reso.sendMessage(to, from, msgEst);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//On informe qu'un nouveau message a été ajouté dans notre liste locale
			notify();
		}
	}

	/*
	 * Méthode appelée lors de la réception de demande de calcul d'estampille
	 */
	void receptionCalEst(Message msg, int from, int to) {

		synchronized(this) {

			try {
				
				//Tant que notre message n'a pas été ajouté dans notre liste locale, on attend
				while(recu.getStockageMessage(msg.getNumMsg()) == null) {
					wait();
				}
				
				est = recu.getStockageMessage(msg.getNumMsg()).getEst();

				nbRcv++;
				nbRcvEst++;
				
				if(est < msg.getEst()) est = msg.getEst();

				
				/*Si on a reçu le calcul d'estampille de toutes les portes, on met notre message en type "ready"
				* et on envoi à toutes les portes une validation*/
				if(nbRcv == nbPorte) {
					StockageMessage sm = new StockageMessage();
					sm.setNumMsg(msg.getNumMsg());
					sm.setTypeMessage(TypeStockageMessage.ready);
					sm.setEst(est);
					sm.setNbRecv(nbRcv);
					sm.setTypeEvenement(msg.getTypeEvenement());
					//System.out.println("Stockage Message :" + sm);
					recu.addStockageMessage(sm);

					Message msgE = new Message();
					msgE.setNumMsg(msg.getNumMsg());
					msgE.setTypeDemande(TypeDemandeMessage.VALIDATION);
					msgE.setEst(est);
					msgE.setTypeEvenement(recu.getStockageMessage(msg.getNumMsg()).getTypeEvenement());

					nbRcv = 0;
					
					for(int i = 0; i < nbPorte; i++) {
						try {
							trace.println("ENVOI: " + msgE + " - de " + numPorte + " a " + i + "|");
							trace.flush();
							reso.sendMessage(numPorte, i, msgE);
						} catch (RemoteException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}

					receptionFinale(msgE, to, from);
					//deliverMsg();
				}
				else {

					StockageMessage sm = new StockageMessage();
					sm.setNumMsg(msg.getNumMsg());
					sm.setEst(est);
					sm.setNbRecv(nbRcv);
					sm.setTypeMessage(TypeStockageMessage.pending);
					sm.setTypeEvenement(msg.getTypeEvenement());
					recu.addStockageMessage(sm);
				}
			}
			catch(Exception e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void receptionFinale(Message msg, int from, int to) {

		ArrayList<StockageMessage>  stockmessages = recu.getStockageMessage();
		
		for(int i = 0; i < stockmessages.size(); i++) {
			
			if (stockmessages.get(i).getNumMsg() == msg.getNumMsg()) {
				
				if(stockmessages.get(i).typeEvenement == TypeDemandeEvenement.ENTREE) {
					nbPlaces--;
				}
				else {
					nbPlaces++;
				}
				
				System.out.println("Message supprime : "+msg.getNumMsg());
				recu.removeStockageMessage(stockmessages.get(i).getNumMsg());
				
			}
			
		}
		
		recu.triByEst();
	}

	
	/*
	 * Méthode permettant de valider les entrées/sorties en triant notre liste locale par ordre d'estampille
	 */
	public void deliverMsg() {

		synchronized(this) {

			try {
				recu.triByEst();
				ArrayList<StockageMessage>  stockmessages = recu.getStockageMessage();

				for(int i = 0; i < stockmessages.size(); i++) {

					if(stockmessages.get(i).getTypeMessage() == TypeStockageMessage.ready) {

						if(stockmessages.get(i).typeEvenement == TypeDemandeEvenement.ENTREE) {
							nbPlaces--;
						}
						else {
							nbPlaces++;
						}
						
						
						System.out.println("Message supprime deliverMsg : "+stockmessages.get(i).getNumMsg());
						recu.removeStockageMessage(stockmessages.get(i).getNumMsg());
					}

				}

			} catch(Exception e) { e.printStackTrace(); }
		}

	}



	@Override
	public void demandeEntree() {
		synchronized(this) {
			diffusion(TypeDemandeEvenement.ENTREE);
		}
	}

	@Override
	public void demandeSortie() {
		synchronized(this) {
			diffusion(TypeDemandeEvenement.SORTIE);
		}

	}

	
	@Override
	public void initialise() throws RemoteException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void receiveMessage(int from, int to, Serializable msg)
			throws RemoteException {
		
		trace.print("RECU: " + msg + " - de " + from + " a " + to + "|");
		trace.flush();
		
		Message message = (Message)msg;
		if(message.getTypeDemande() == TypeDemandeMessage.INITIALISATION) receptionDiffusion(message, from, to);
		if(message.getTypeDemande() == TypeDemandeMessage.CALCUL_EST) receptionCalEst(message, from, to);
		if(message.getTypeDemande() == TypeDemandeMessage.VALIDATION) receptionFinale(message, from, to); 
	}
}
